package com.muyu.danmo.xxl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.muyu.danmo.util.MethodInvoker;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

@RequiredArgsConstructor
@Component
@Slf4j
public class CacheRebuilder implements ApplicationContextAware {

    private final RedisTemplate<String, Object> redisTemplate;
    private ApplicationContext applicationContext;

    private final ExpressionParser parser = new SpelExpressionParser();

    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
    }

    @XxlJob("cacheRebuilderTask")
    public void rebuild() {
        Set<String> keys = redisTemplate.keys("cache:meta:*");
        if (CollUtil.isEmpty(keys)) {
            return;
        }

        MethodInvoker invoker = new MethodInvoker(applicationContext);

        for (String metaKey : keys) {
            Map<Object, Object> all = redisTemplate.opsForHash().entries(metaKey);
            for (Map.Entry<Object, Object> entry : all.entrySet()) {
                JSONObject meta = JSONUtil.parseObj(entry.getValue());

                try {
                    String className = meta.getStr("class");
                    String methodName = meta.getStr("method");
                    long ttl = meta.getLong("ttl");
                    String keyExpr = meta.getStr("keyExpr");
                    JSONArray jsonArgs = meta.getJSONArray("args");

                    // 参数反序列化
                    Object[] args = jsonArgs.toArray();

                    // 构建 SpEL 上下文（使用反射拿参数名）
                    Class<?> targetClass = Class.forName(className);
                    Method method = findMethod(targetClass, methodName, args.length);
                    String[] paramNames = new DefaultParameterNameDiscoverer().getParameterNames(method);

                    EvaluationContext context = new StandardEvaluationContext();
                    if (paramNames != null) {
                        for (int i = 0; i < paramNames.length; i++) {
                            context.setVariable(paramNames[i], args[i]);
                        }
                    }

                    // 解析 key
                    String evaluatedKey = parser.parseExpression(keyExpr).getValue(context, String.class);
                    // 从 metaKey 取出 cacheName
                    String cacheName = metaKey.replace("cache:meta:", "");
                    String realKey = "cache:" + cacheName + "::" + DigestUtils.md5DigestAsHex(evaluatedKey.getBytes());

                    // 如果已存在则跳过
                    if (Boolean.TRUE.equals(redisTemplate.hasKey(realKey))) {
                        continue;
                    }

                    // 执行方法获取结果
                    Object result = invoker.invoke(className, methodName, jsonArgs);

                    long randomSeconds = RandomUtil.randomLong(50, 300);
                    redisTemplate.opsForValue().set(realKey, result, Duration.ofSeconds(ttl + randomSeconds));
                    log.info("缓存重建成功: {}", realKey);

                } catch (Exception e) {
                    log.error("缓存重建失败: {}", entry.getKey(), e);
                }
            }
        }
    }

    private Method findMethod(Class<?> clazz, String methodName, int argCount) {
        return Arrays.stream(clazz.getDeclaredMethods())
                .filter(m -> m.getName().equals(methodName) && m.getParameterCount() == argCount)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("找不到方法: " + methodName));
    }
}

